/* GStreamer
 * Copyright (C) 2007 David Schleef <ds@schleef.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#if 1
#include "precomp.hpp"
#include <gst/gst.h>
#include <gst/base/gstbasesink.h>
#include <gst/gstbuffer.h>

#include <string.h>

#include "gstappsink.h"

GST_DEBUG_CATEGORY (app_sink_debug);
#define GST_CAT_DEFAULT app_sink_debug

static const GstElementDetails app_sink_details =
	GST_ELEMENT_DETAILS ((gchar*)"AppSink",
						 (gchar*)"Generic/Sink",
						 (gchar*)"Allow the application to get access to raw buffer",
						 (gchar*)"David Schleef <ds@schleef.org>, Wim Taymans <wim.taymans@gmail.com");

enum {
	/* signals */
	SIGNAL_EOS,
	SIGNAL_NEW_PREROLL,
	SIGNAL_NEW_BUFFER,

	/* acions */
	SIGNAL_PULL_PREROLL,
	SIGNAL_PULL_BUFFER,

	LAST_SIGNAL
};

enum {
	PROP_0,
	PROP_CAPS,
	PROP_EOS
};

static GstStaticPadTemplate gst_app_sink_template =
	GST_STATIC_PAD_TEMPLATE ("sink",
							 GST_PAD_SINK,
							 GST_PAD_ALWAYS,
							 GST_STATIC_CAPS_ANY);

static void gst_app_sink_dispose (GObject* object);
static void gst_app_sink_finalize (GObject* object);

static void gst_app_sink_set_property (GObject* object, guint prop_id,
									   const GValue* value, GParamSpec* pspec);
static void gst_app_sink_get_property (GObject* object, guint prop_id,
									   GValue* value, GParamSpec* pspec);

static gboolean gst_app_sink_start (GstBaseSink* psink);
static gboolean gst_app_sink_stop (GstBaseSink* psink);
static gboolean gst_app_sink_event (GstBaseSink* sink, GstEvent* event);
static GstFlowReturn gst_app_sink_preroll (GstBaseSink* psink,
		GstBuffer* buffer);
static GstFlowReturn gst_app_sink_render (GstBaseSink* psink,
		GstBuffer* buffer);
static GstCaps* gst_app_sink_getcaps (GstBaseSink* psink);

static guint gst_app_sink_signals[LAST_SIGNAL] = { 0 };

GST_BOILERPLATE (GstAppSink, gst_app_sink, GstBaseSink, GST_TYPE_BASE_SINK);

static gboolean
appsink_plugin_init (GstPlugin* plugin) {
	GST_DEBUG_CATEGORY_INIT (app_sink_debug, "opencv-appsink", 0, "Application sink");

	if (!gst_element_register (plugin, "opencv-appsink", GST_RANK_PRIMARY,
							   gst_app_sink_get_type ())) {
		return FALSE;
	}

	return TRUE;
}

#undef PACKAGE
#define PACKAGE "highgui"
GST_PLUGIN_DEFINE_STATIC (GST_VERSION_MAJOR, GST_VERSION_MINOR,
						  "opencv-appsink", "Element application sink",
						  appsink_plugin_init, "0.1", "LGPL", "OpenCV's internal copy of gstappsink", "OpenCV")

void
gst_app_marshal_OBJECT__VOID (GClosure* closure,
							  GValue* return_value,
							  guint n_param_values,
							  const GValue* param_values,
							  gpointer invocation_hint, gpointer marshal_data) {
	typedef GstBuffer *(*GMarshalFunc_OBJECT__VOID) (gpointer data1,
			gpointer data2);
	register GMarshalFunc_OBJECT__VOID callback;
	register GCClosure* cc = (GCClosure*) closure;
	register gpointer data1, data2;
	GstBuffer* v_return;

	//printf("appmarshalobject\n");

	g_return_if_fail (return_value != NULL);
	g_return_if_fail (n_param_values == 1);

	if (G_CCLOSURE_SWAP_DATA (closure)) {
		data1 = closure->data;
		data2 = g_value_peek_pointer (param_values + 0);
	} else {
		data1 = g_value_peek_pointer (param_values + 0);
		data2 = closure->data;
	}
	callback =
		(GMarshalFunc_OBJECT__VOID) (marshal_data ? marshal_data : cc->callback);

	v_return = callback (data1, data2);

	gst_value_take_buffer (return_value, v_return);
}

static void
gst_app_sink_base_init (gpointer g_class) {
	GstElementClass* element_class = GST_ELEMENT_CLASS (g_class);

	//printf("appsinkbaseinit\n");

	GST_DEBUG_CATEGORY_INIT (app_sink_debug, "appsink", 0, "appsink element");

	gst_element_class_set_details (element_class, &app_sink_details);

	gst_element_class_add_pad_template (element_class,
										gst_static_pad_template_get (&gst_app_sink_template));
}

static void
gst_app_sink_class_init (GstAppSinkClass* klass) {
	GObjectClass* gobject_class = (GObjectClass*) klass;
	GstBaseSinkClass* basesink_class = (GstBaseSinkClass*) klass;

	//printf("appsinkclassinit\n");

	gobject_class->dispose = gst_app_sink_dispose;
	gobject_class->finalize = gst_app_sink_finalize;

	gobject_class->set_property = gst_app_sink_set_property;
	gobject_class->get_property = gst_app_sink_get_property;

	g_object_class_install_property (gobject_class, PROP_CAPS,
									 g_param_spec_boxed ("caps", "Caps",
											 "The caps of the sink pad", GST_TYPE_CAPS, (GParamFlags)G_PARAM_READWRITE));

	g_object_class_install_property (gobject_class, PROP_EOS,
									 g_param_spec_boolean ("eos", "EOS",
											 "Check if the sink is EOS", TRUE, G_PARAM_READABLE));

	/**
	 * GstAppSink::eos:
	 * @appsink: the appsink element that emitted the signal
	 *
	 * Signal that the end-of-stream has been reached.
	 */
	gst_app_sink_signals[SIGNAL_EOS] =
		g_signal_new ("eos", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
					  G_STRUCT_OFFSET (GstAppSinkClass, eos),
					  NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE);
	/**
	 * GstAppSink::new-preroll:
	 * @appsink: the appsink element that emitted the signal
	 * @buffer: the buffer that caused the preroll
	 *
	 * Signal that a new preroll buffer is available.
	 */
	gst_app_sink_signals[SIGNAL_NEW_PREROLL] =
		g_signal_new ("new-preroll", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
					  G_STRUCT_OFFSET (GstAppSinkClass, new_preroll),
					  NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1,
					  GST_TYPE_BUFFER);
	/**
	 * GstAppSink::new-buffer:
	 * @appsink: the appsink element that emitted the signal
	 * @buffer: the buffer that is available
	 *
	 * Signal that a new buffer is available.
	 */
	gst_app_sink_signals[SIGNAL_NEW_BUFFER] =
		g_signal_new ("new-buffer", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
					  G_STRUCT_OFFSET (GstAppSinkClass, new_buffer),
					  NULL, NULL, g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1,
					  G_TYPE_UINT);

	/**
	 * GstAppSink::pull-preroll:
	 * @appsink: the appsink element to emit this signal on
	 *
	 * Get the last preroll buffer on @appsink.
	 */
	gst_app_sink_signals[SIGNAL_PULL_PREROLL] =
		g_signal_new ("pull-preroll", G_TYPE_FROM_CLASS (klass),
					  G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstAppSinkClass, pull_preroll), NULL,
					  NULL, gst_app_marshal_OBJECT__VOID, GST_TYPE_BUFFER, 0, G_TYPE_NONE);
	/**
	 * GstAppSink::pull-buffer:
	 * @appsink: the appsink element to emit this signal on
	 *
	 * Get the next buffer buffer on @appsink.
	 */
	gst_app_sink_signals[SIGNAL_PULL_PREROLL] =
		g_signal_new ("pull-buffer", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
					  G_STRUCT_OFFSET (GstAppSinkClass, pull_buffer),
					  NULL, NULL, gst_app_marshal_OBJECT__VOID, GST_TYPE_BUFFER, 0,
					  G_TYPE_NONE);

	basesink_class->start = gst_app_sink_start;
	basesink_class->stop = gst_app_sink_stop;
	basesink_class->event = gst_app_sink_event;
	basesink_class->preroll = gst_app_sink_preroll;
	basesink_class->render = gst_app_sink_render;
	basesink_class->get_caps = gst_app_sink_getcaps;

	klass->pull_preroll = gst_app_sink_pull_preroll;
	klass->pull_buffer = gst_app_sink_pull_buffer;
}

static void
gst_app_sink_init (GstAppSink* appsink, GstAppSinkClass* klass) {
	appsink->mutex = g_mutex_new ();
	appsink->cond = g_cond_new ();
	appsink->queue = g_queue_new ();
}

static void
gst_app_sink_dispose (GObject* obj) {
	GstAppSink* appsink = GST_APP_SINK (obj);
	GstBuffer* buffer;

	//printf("appsinkdispose\n");

	if (appsink->caps) {
		gst_caps_unref (appsink->caps);
		appsink->caps = NULL;
	}
	if (appsink->preroll) {
		gst_buffer_unref (appsink->preroll);
		appsink->preroll = NULL;
	}
	g_mutex_lock (appsink->mutex);
	while ((buffer = (GstBuffer*)g_queue_pop_head (appsink->queue))) {
		gst_buffer_unref (buffer);
	}
	g_mutex_unlock (appsink->mutex);

	G_OBJECT_CLASS (parent_class)->dispose (obj);
}

static void
gst_app_sink_finalize (GObject* obj) {
	GstAppSink* appsink = GST_APP_SINK (obj);

	g_mutex_free (appsink->mutex);
	g_cond_free (appsink->cond);
	g_queue_free (appsink->queue);

	G_OBJECT_CLASS (parent_class)->finalize (obj);
}

static void
gst_app_sink_set_property (GObject* object, guint prop_id,
						   const GValue* value, GParamSpec* pspec) {
	GstAppSink* appsink = GST_APP_SINK (object);

	//printf("appsinksetproperty\n");

	switch (prop_id) {
	case PROP_CAPS:
		gst_app_sink_set_caps (appsink, gst_value_get_caps (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
gst_app_sink_get_property (GObject* object, guint prop_id, GValue* value,
						   GParamSpec* pspec) {
	GstAppSink* appsink = GST_APP_SINK (object);

	//printf("appsinkgetproperty\n");

	switch (prop_id) {
	case PROP_CAPS: {
		GstCaps* caps;

		caps = gst_app_sink_get_caps (appsink);
		gst_value_set_caps (value, caps);
		if (caps) {
			gst_caps_unref (caps);
		}
		break;
	}
	case PROP_EOS:
		g_value_set_boolean (value, gst_app_sink_is_eos (appsink));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
gst_app_sink_flush_unlocked (GstAppSink* appsink) {
	GstBuffer* buffer;

	//printf("appsinkflushunlocked\n");

	GST_DEBUG_OBJECT (appsink, "flushing appsink");
	appsink->is_eos = FALSE;
	gst_buffer_replace (&appsink->preroll, NULL);
	while ((buffer = (GstBuffer*)g_queue_pop_head (appsink->queue))) {
		gst_buffer_unref (buffer);
	}
	g_cond_signal (appsink->cond);
}

static gboolean
gst_app_sink_start (GstBaseSink* psink) {
	GstAppSink* appsink = GST_APP_SINK (psink);

	//printf("appsinkstart\n");

	g_mutex_lock (appsink->mutex);
	appsink->is_eos = FALSE;
	appsink->started = TRUE;
	GST_DEBUG_OBJECT (appsink, "starting");
	g_mutex_unlock (appsink->mutex);

	return TRUE;
}

static gboolean
gst_app_sink_stop (GstBaseSink* psink) {
	GstAppSink* appsink = GST_APP_SINK (psink);

	//printf("appsinkstop\n");

	g_mutex_lock (appsink->mutex);
	GST_DEBUG_OBJECT (appsink, "stopping");
	appsink->started = FALSE;
	gst_app_sink_flush_unlocked (appsink);
	g_mutex_unlock (appsink->mutex);

	return TRUE;
}

static gboolean
gst_app_sink_event (GstBaseSink* sink, GstEvent* event) {
	GstAppSink* appsink = GST_APP_SINK (sink);

	//printf("appsinkevent\n");

	switch (event->type) {
	case GST_EVENT_EOS:
		g_mutex_lock (appsink->mutex);
		GST_DEBUG_OBJECT (appsink, "receiving EOS");
		appsink->is_eos = TRUE;
		g_cond_signal (appsink->cond);
		g_mutex_unlock (appsink->mutex);
		break;
	case GST_EVENT_FLUSH_START:
		break;
	case GST_EVENT_FLUSH_STOP:
		g_mutex_lock (appsink->mutex);
		GST_DEBUG_OBJECT (appsink, "received FLUSH_STOP");
		gst_app_sink_flush_unlocked (appsink);
		g_mutex_unlock (appsink->mutex);
		break;
	default:
		break;
	}
	return TRUE;
}

static GstFlowReturn
gst_app_sink_preroll (GstBaseSink* psink, GstBuffer* buffer) {
	GstAppSink* appsink = GST_APP_SINK (psink);

	//printf("appsinkpreroll\n");

	g_mutex_lock (appsink->mutex);
	GST_DEBUG_OBJECT (appsink, "setting preroll buffer %p", buffer);
	gst_buffer_replace (&appsink->preroll, buffer);
	g_cond_signal (appsink->cond);
	g_mutex_unlock (appsink->mutex);

	g_signal_emit(psink, gst_app_sink_signals[SIGNAL_NEW_PREROLL], 0, buffer);

	return GST_FLOW_OK;
}

static GstFlowReturn
gst_app_sink_render (GstBaseSink* psink, GstBuffer* buffer) {
	GstAppSink* appsink = GST_APP_SINK (psink);

	g_mutex_lock (appsink->mutex);
	GST_DEBUG_OBJECT (appsink, "pushing render buffer %p on queue", buffer);
	g_queue_push_tail (appsink->queue, gst_buffer_ref (buffer));
	g_cond_signal (appsink->cond);
//  printf("appsinkrender, have %d buffers\n", g_queue_get_length(appsink->queue));
	g_mutex_unlock (appsink->mutex);
	g_signal_emit(psink, gst_app_sink_signals[SIGNAL_NEW_BUFFER], 0,
				  g_queue_get_length(appsink->queue));

	return GST_FLOW_OK;
}

static GstCaps *
gst_app_sink_getcaps (GstBaseSink* psink) {
	GstCaps* caps;

	//printf("appsinkgetcaps\n");

	GstAppSink* appsink = GST_APP_SINK (psink);

	GST_OBJECT_LOCK (appsink);
	if ((caps = appsink->caps)) {
		gst_caps_ref (caps);
	}
	GST_DEBUG_OBJECT (appsink, "got caps %" GST_PTR_FORMAT, caps);
	GST_OBJECT_UNLOCK (appsink);

	return caps;
}

/* external API */

/**
 * gst_app_sink_set_caps:
 * @appsink: a #GstAppSink
 * @caps: caps to set
 *
 * Set the capabilities on the appsink element.  This function takes
 * a copy of the caps structure. After calling this method, the sink will only
 * accept caps that match @caps. If @caps is non-fixed, you must check the caps
 * on the buffers to get the actual used caps.
 */
void
gst_app_sink_set_caps (GstAppSink* appsink, const GstCaps* caps) {
	GstCaps* old;

	g_return_if_fail (appsink != NULL);
	g_return_if_fail (GST_IS_APP_SINK (appsink));

	GST_OBJECT_LOCK (appsink);
	GST_DEBUG_OBJECT (appsink, "setting caps to %" GST_PTR_FORMAT, caps);
	old = appsink->caps;
	if (caps) {
		appsink->caps = gst_caps_copy (caps);
	} else {
		appsink->caps = NULL;
	}
	if (old) {
		gst_caps_unref (old);
	}
	GST_OBJECT_UNLOCK (appsink);
}

/**
 * gst_app_sink_get_caps:
 * @appsink: a #GstAppSink
 *
 * Get the configured caps on @appsink.
 *
 * Returns: the #GstCaps accepted by the sink. gst_caps_unref() after usage.
 */
GstCaps *
gst_app_sink_get_caps (GstAppSink* appsink) {
	GstCaps* caps;

	g_return_val_if_fail (appsink != NULL, NULL);
	g_return_val_if_fail (GST_IS_APP_SINK (appsink), NULL);

	GST_OBJECT_LOCK (appsink);
	if ((caps = appsink->caps)) {
		gst_caps_ref (caps);
	}
	GST_DEBUG_OBJECT (appsink, "getting caps of %" GST_PTR_FORMAT, caps);
	GST_OBJECT_UNLOCK (appsink);

	return caps;
}

/**
 * gst_app_sink_is_eos:
 * @appsink: a #GstAppSink
 *
 * Check if @appsink is EOS, which is when no more buffers can be pulled because
 * an EOS event was received.
 *
 * This function also returns %TRUE when the appsink is not in the PAUSED or
 * PLAYING state.
 *
 * Returns: %TRUE if no more buffers can be pulled and the appsink is EOS.
 */
gboolean
gst_app_sink_is_eos (GstAppSink* appsink) {
	gboolean ret;

	g_return_val_if_fail (appsink != NULL, FALSE);
	g_return_val_if_fail (GST_IS_APP_SINK (appsink), FALSE);

	g_mutex_lock (appsink->mutex);
	if (!appsink->started) {
		goto not_started;
	}

	if (appsink->is_eos && g_queue_is_empty (appsink->queue)) {
		GST_DEBUG_OBJECT (appsink, "we are EOS and the queue is empty");
		ret = TRUE;
	} else {
		GST_DEBUG_OBJECT (appsink, "we are not yet EOS");
		ret = FALSE;
	}
	g_mutex_unlock (appsink->mutex);

	return ret;

not_started: {
		GST_DEBUG_OBJECT (appsink, "we are stopped, return TRUE");
		g_mutex_unlock (appsink->mutex);
		return TRUE;
	}
}

/**
 * gst_app_sink_pull_preroll:
 * @appsink: a #GstAppSink
 *
 * Get the last preroll buffer in @appsink. This was the buffer that caused the
 * appsink to preroll in the PAUSED state. This buffer can be pulled many times
 * and remains available to the application even after EOS.
 *
 * This function is typically used when dealing with a pipeline in the PAUSED
 * state. Calling this function after doing a seek will give the buffer right
 * after the seek position.
 *
 * Note that the preroll buffer will also be returned as the first buffer
 * when calling gst_app_sink_pull_buffer().
 *
 * If an EOS event was received before any buffers, this function returns
 * %NULL. Use gst_app_sink_is_eos () to check for the EOS condition.
 *
 * This function blocks until a preroll buffer or EOS is received or the appsink
 * element is set to the READY/NULL state.
 *
 * Returns: a #GstBuffer or NULL when the appsink is stopped or EOS.
 */
GstBuffer *
gst_app_sink_pull_preroll (GstAppSink* appsink) {
	GstBuffer* buf = NULL;

	//printf("pull_preroll\n");

	g_return_val_if_fail (appsink != NULL, NULL);
	g_return_val_if_fail (GST_IS_APP_SINK (appsink), NULL);

	g_mutex_lock (appsink->mutex);

	while (TRUE) {
		GST_DEBUG_OBJECT (appsink, "trying to grab a buffer");
		if (!appsink->started) {
			goto not_started;
		}

		if (appsink->preroll != NULL) {
			break;
		}

		if (appsink->is_eos) {
			goto eos;
		}

		/* nothing to return, wait */
		GST_DEBUG_OBJECT (appsink, "waiting for the preroll buffer");
		g_cond_wait (appsink->cond, appsink->mutex);
	}
	buf = gst_buffer_ref (appsink->preroll);
	GST_DEBUG_OBJECT (appsink, "we have the preroll buffer %p", buf);
	g_mutex_unlock (appsink->mutex);

	return buf;

	/* special conditions */
eos: {
		GST_DEBUG_OBJECT (appsink, "we are EOS, return NULL");
		g_mutex_unlock (appsink->mutex);
		return NULL;
	}
not_started: {
		GST_DEBUG_OBJECT (appsink, "we are stopped, return NULL");
		g_mutex_unlock (appsink->mutex);
		return NULL;
	}
}

/**
 * gst_app_sink_pull_buffer:
 * @appsink: a #GstAppSink
 *
 * This function blocks until a buffer or EOS becomes available or the appsink
 * element is set to the READY/NULL state.
 *
 * This function will only return buffers when the appsink is in the PLAYING
 * state. All rendered buffers will be put in a queue so that the application
 * can pull buffers at its own rate. Note that when the application does not
 * pull buffers fast enough, the queued buffers could consume a lot of memory,
 * especially when dealing with raw video frames.
 *
 * If an EOS event was received before any buffers, this function returns
 * %NULL. Use gst_app_sink_is_eos () to check for the EOS condition.
 *
 * Returns: a #GstBuffer or NULL when the appsink is stopped or EOS.
 */
GstBuffer *
gst_app_sink_pull_buffer (GstAppSink* appsink) {
	GstBuffer* buf = NULL;

	//printf("pull_buffer\n");

	g_return_val_if_fail (appsink != NULL, NULL);
	g_return_val_if_fail (GST_IS_APP_SINK (appsink), NULL);

	g_mutex_lock (appsink->mutex);

	while (TRUE) {
		GST_DEBUG_OBJECT (appsink, "trying to grab a buffer");
		if (!appsink->started) {
			goto not_started;
		}

		if (!g_queue_is_empty (appsink->queue)) {
			break;
		}

		if (appsink->is_eos) {
			goto eos;
		}

		/* nothing to return, wait */
		GST_DEBUG_OBJECT (appsink, "waiting for a buffer");
		g_cond_wait (appsink->cond, appsink->mutex);
	}
	buf = (GstBuffer*)g_queue_pop_head (appsink->queue);
	GST_DEBUG_OBJECT (appsink, "we have a buffer %p", buf);
	g_mutex_unlock (appsink->mutex);

	return buf;

	/* special conditions */
eos: {
		GST_DEBUG_OBJECT (appsink, "we are EOS, return NULL");
		g_mutex_unlock (appsink->mutex);
		return NULL;
	}
not_started: {
		GST_DEBUG_OBJECT (appsink, "we are stopped, return NULL");
		g_mutex_unlock (appsink->mutex);
		return NULL;
	}
}

/**
 * gst_app_sink_peek_buffer:
 * @appsink: a #GstAppSink
 *
 * This function returns a buffer if there is one queued but does not block.
 *
 * This function will only return buffers when the appsink is in the PLAYING
 * state. All rendered buffers will be put in a queue so that the application
 * can pull buffers at its own rate. Note that when the application does not
 * pull buffers fast enough, the queued buffers could consume a lot of memory,
 * especially when dealing with raw video frames.
 *
 * If an EOS event was received before any buffers, this function returns
 * %NULL. Use gst_app_sink_is_eos () to check for the EOS condition.
 *
 * Returns: a #GstBuffer or NULL when the appsink is stopped or EOS.
 */
GstBuffer *
gst_app_sink_peek_buffer (GstAppSink* appsink) {
	GstBuffer* buf = NULL;

	//printf("pull_buffer\n");

	g_return_val_if_fail (appsink != NULL, NULL);
	g_return_val_if_fail (GST_IS_APP_SINK (appsink), NULL);

	g_mutex_lock (appsink->mutex);

	GST_DEBUG_OBJECT (appsink, "trying to grab a buffer");
	if (!appsink->started) {
		goto not_started;
	}

	if (g_queue_is_empty (appsink->queue)) {
		return NULL;
	}

	if (appsink->is_eos) {
		goto eos;
	}

	/* nothing to return, wait */
	buf = (GstBuffer*)g_queue_pop_head (appsink->queue);
	GST_DEBUG_OBJECT (appsink, "we have a buffer %p", buf);
	g_mutex_unlock (appsink->mutex);

	return buf;

	/* special conditions */
eos: {
		GST_DEBUG_OBJECT (appsink, "we are EOS, return NULL");
		g_mutex_unlock (appsink->mutex);
		return NULL;
	}
not_started: {
		GST_DEBUG_OBJECT (appsink, "we are stopped, return NULL");
		g_mutex_unlock (appsink->mutex);
		return NULL;
	}
}

guint
gst_app_sink_get_queue_length (GstAppSink* appsink) {
	return g_queue_get_length (appsink->queue);
}
#endif
